import * as path from "path";
import * as fs from "fs";
import * as vscode from "vscode";
import { writeFileText } from "./common/system";
import { DefaultOptions, DsProperties, DsWindowProperties, DsWorkspaceProperties } from "./common/types";

const dsSettings = vscode.workspace.getConfiguration("ds");

function getSettingValue<K extends keyof DsProperties, R = DsProperties[K]>(key: K, defaultValue: R): R {
    return dsSettings.get(key, defaultValue);
}

export interface WorkspaceConfiguration {
    readonly root: string;
    readonly storagePath: string;
    readonly jsonPath: string;
    readonly onPropertyJsonChanged: vscode.Event<DsWorkspaceProperties>;
    value(): DsWorkspaceProperties;
    update<K extends keyof DsWorkspaceProperties>(key: K, value: DsWorkspaceProperties[K]): void;
    dispose(): void;
}

export interface ConfigurationManager {
    readonly onWorkspacePropertyJsonChanged: vscode.Event<DsWorkspaceProperties>;
    getWindowConfiguration(): DsWindowProperties;
    getWindowConfiguration<K extends keyof DsProperties>(key: K, defaultValue: DsProperties[K]): DsProperties[K];
    getWorkspaceConfiguration(workspaceRoot?: string): DsProperties;
    addWorkspace(workspaceRoot: string): WorkspaceConfiguration;
    removeWorkspace(workspaceRoot: string): void;
    getWorkspaceProperties(workspaceRoot: string): DsWorkspaceProperties;
    updateWorkspaceProperties<K extends keyof DsWorkspaceProperties>(workspaceRoot: string, key: K, value: DsWorkspaceProperties[K]): void;
    dispose(): void;
}

function getDsWindowConfiguration(): DsWindowProperties {
    return {
        "workspaceFolderNames": getSettingValue("workspaceFolderNames", DefaultOptions.workspaceFolderNames.join(";")),
        "userName": getSettingValue("userName", DefaultOptions.mdmLoaderOptions.userName),
        "codeFolding": getSettingValue("codeFolding", DefaultOptions.codeFolding),
        "metadataContext": getSettingValue("metadataContext", DefaultOptions.metadataContext),
        "metadataLabelType": getSettingValue("metadataLabelType", DefaultOptions.metadataLabelType),
        "metadataLanguage": getSettingValue("metadataLanguage", DefaultOptions.metadataLanguage.longCode),
        "promptMetadataWhenOpenFile": getSettingValue("promptMetadataWhenOpenFile", DefaultOptions.promptMetadataWhenOpenFile),
        "closeMddAfterSerialization": getSettingValue("closeMddAfterSerialization", DefaultOptions.closeMddAfterSerialization),
    };
}

function getDsConfiguration(): DsProperties {
    return {
        "workspaceFolderNames": getSettingValue("workspaceFolderNames", DefaultOptions.workspaceFolderNames.join(";")),
        "userName": getSettingValue("userName", DefaultOptions.mdmLoaderOptions.userName),
        "mdmLoader.mdmVersion": getSettingValue("mdmLoader.mdmVersion", DefaultOptions.mdmLoaderOptions.mdmVersion),
        "metadataContext": getSettingValue("metadataContext", DefaultOptions.metadataContext),
        "metadataLabelType": getSettingValue("metadataLabelType", DefaultOptions.metadataLabelType),
        "metadataLanguage": getSettingValue("metadataLanguage", DefaultOptions.metadataLanguage.longCode),
        "codeFolding": getSettingValue("codeFolding", DefaultOptions.codeFolding),
        "promptMetadataWhenOpenFile": getSettingValue("promptMetadataWhenOpenFile", DefaultOptions.promptMetadataWhenOpenFile),
        "closeMddAfterSerialization": getSettingValue("closeMddAfterSerialization", DefaultOptions.closeMddAfterSerialization),
        "mdmSerializer.indentSpaceCount": getSettingValue("mdmSerializer.indentSpaceCount", DefaultOptions.mdmSerializerOptions.indentSpaceCount),
        "mdmSerializer.invalidValueLowerBoundary": getSettingValue("mdmSerializer.invalidValueLowerBoundary", DefaultOptions.mdmSerializerOptions.invalidValueLowerBoundary),
        "mdmSerializer.invalidValueUpperBoundary": getSettingValue("mdmSerializer.invalidValueUpperBoundary", DefaultOptions.mdmSerializerOptions.invalidValueUpperBoundary),
        "mdmSerializer.lineFeedCharater": getSettingValue("mdmSerializer.lineFeedCharater", DefaultOptions.mdmSerializerOptions.lineFeedCharater),
        "mdmSerializer.hideLabeledObjectProperties": getSettingValue("mdmSerializer.hideLabeledObjectProperties", DefaultOptions.mdmSerializerOptions.hideLabeledObjectProperties),
        "mdmLoader.checkAliasConflict": getSettingValue("mdmLoader.checkAliasConflict", DefaultOptions.mdmLoaderOptions.checkAliasConflict),
        "mdmLoader.defaultContext": getSettingValue("mdmLoader.defaultContext", DefaultOptions.mdmLoaderOptions.defaultContext),
        "mdmLoader.defaultLabelType": getSettingValue("mdmLoader.defaultLabelType", DefaultOptions.mdmLoaderOptions.defaultLabelType),
        "mdmLoader.defaultLanguage": getSettingValue("mdmLoader.defaultLanguage", DefaultOptions.mdmLoaderOptions.defaultLanguage.longCode),
        "mdmLoader.defaultRoutingContext": getSettingValue("mdmLoader.defaultRoutingContext", DefaultOptions.mdmLoaderOptions.defaultRoutingContext),
        "mdmLoader.defaultScriptType": getSettingValue("mdmLoader.defaultScriptType", DefaultOptions.mdmLoaderOptions.defaultScriptType),
        "mdmLoader.invalidValueLowerBoundary": getSettingValue("mdmLoader.invalidValueLowerBoundary", DefaultOptions.mdmLoaderOptions.invalidValueLowerBoundary),
        "mdmLoader.invalidValueUpperBoundary": getSettingValue("mdmLoader.invalidValueUpperBoundary", DefaultOptions.mdmLoaderOptions.invalidValueUpperBoundary),
    };
}

export const propertiesJsonPattern: string = "ds_properties.json";

export interface DsPropertiesChangedHandler {
    (configuration: DsProperties): void;
}

function createWorkspaceConfiguration(workspaceRoot: string, storeJson: boolean): WorkspaceConfiguration {
    const disposables: vscode.Disposable[] = [];
    const storagePath = path.join(workspaceRoot, ".vscode");
    const jsonPath = path.join(storagePath, propertiesJsonPattern);
    const changeEmitter = new vscode.EventEmitter<DsWorkspaceProperties>();
    disposables.push(changeEmitter);
    const watcher = vscode.workspace.createFileSystemWatcher(jsonPath);
    disposables.push(watcher);

    const jsonObj: DsWorkspaceProperties = {
        "mdmSerializer.indentSpaceCount": getSettingValue("mdmSerializer.indentSpaceCount", DefaultOptions.mdmSerializerOptions.indentSpaceCount),
        "mdmSerializer.lineFeedCharater": getSettingValue("mdmSerializer.lineFeedCharater", DefaultOptions.mdmSerializerOptions.lineFeedCharater),
    };

    ensurePropertiesJsonFile();
    tryParseJson();

    watcher.onDidChange(_ => {
        tryParseJson();
        changeEmitter.fire(jsonObj);
    });

    return {
        root: workspaceRoot,
        storagePath,
        jsonPath,
        onPropertyJsonChanged: changeEmitter.event,
        value() {
            return Object.assign({}, jsonObj);
        },
        update(key, value) {
            jsonObj[key] = value;
            if (value === undefined) {
                delete jsonObj[key];
            }
            saveJson();
        },
        dispose() {
            disposables.forEach(d => d.dispose());
            disposables.splice(0);
        },
    };

    function ensurePropertiesJsonFile(writeJson: boolean = true) {
        if (!storeJson || (jsonPath && fs.existsSync(jsonPath) && fs.statSync(jsonPath).isFile())) {
            return;
        }
        try {
            if (!fs.existsSync(storagePath) || !fs.statSync(storagePath).isDirectory()) {
                fs.mkdirSync(storagePath);
            }
            if (writeJson) {
                writeFileText(jsonPath, JSON.stringify(jsonObj, undefined, 4), "utf-8");
            }
        }
        catch (error) {
            if (error instanceof Error) {
                vscode.window.showErrorMessage(`错误: 创建文件 (${jsonPath}) 失败: ${error.message}`);
            }
            else {
                throw error;
            }
        }
    }

    function tryParseJson() {
        if (!storeJson) {
            return true;
        }

        let success = true;

        try {
            const jsonText = fs.readFileSync(jsonPath, "utf8");
            if (jsonText === "") {
                return false;
            }

            const parsedObj = JSON.parse(jsonText);
            if (!parsedObj) {
                vscode.window.showErrorMessage(`无效的配置文件: '${jsonPath}'.`);
                return false;
            }

            // metadata文件路径由本地json对象配置。
            if (parsedObj.metadata && typeof parsedObj.metadata === "string") {
                jsonObj.metadata = parsedObj.metadata;
            }
            if (parsedObj["mdmSerializer.indentSpaceCount"] !== undefined && Number.isInteger(parsedObj["mdmSerializer.indentSpaceCount"])) {
                jsonObj["mdmSerializer.indentSpaceCount"] = jsonObj["mdmSerializer.indentSpaceCount"];
            }
            if (parsedObj["mdmSerializer.lineFeedCharater"] && typeof parsedObj["mdmSerializer.lineFeedCharater"] === "string") {
                let lineFeed = jsonObj["mdmSerializer.lineFeedCharater"];
                if (lineFeed === "LF") {
                    jsonObj["mdmSerializer.lineFeedCharater"] = "\n";
                }
                else if (lineFeed === "CRLF") {
                    jsonObj["mdmSerializer.lineFeedCharater"] = "\r\n";
                }
            }
        }
        catch (error) {
            if (error instanceof Error) {
                vscode.window.showErrorMessage(`文件 (${jsonPath}) 解析失败: ${error.message}`);
                success = false;
            }
            else {
                throw error;
            }
        }

        return success;
    }

    function saveJson() {
        if (!storeJson) {
            return;
        }
        ensurePropertiesJsonFile(false);
        writeFileText(jsonPath, JSON.stringify(jsonObj, undefined, 4), "utf-8");
    }

}

export function createConfigurationManager(): ConfigurationManager {
    const workspaces = new Map<string, WorkspaceConfiguration>();
    let disposables: vscode.Disposable[] = [];
    const workspacePropertyJsonChangedEmitter = new vscode.EventEmitter<DsWorkspaceProperties>();

    return {
        onWorkspacePropertyJsonChanged: workspacePropertyJsonChangedEmitter.event,
        getWindowConfiguration,
        getWorkspaceConfiguration,
        addWorkspace,
        removeWorkspace,
        getWorkspaceProperties,
        updateWorkspaceProperties,
        dispose,
    };

    function getWindowConfiguration(): DsWindowProperties;
    function getWindowConfiguration<K extends keyof DsProperties>(key: K, defaultValue: DsProperties[K]): DsProperties;
    function getWindowConfiguration<K extends keyof DsProperties>(key?: K, defaultValue?: DsProperties[K]) {
        if (key !== undefined && defaultValue !== undefined) {
            return getSettingValue(key, defaultValue);
        }
        return getDsWindowConfiguration();
    }

    function getWorkspaceConfiguration(workspaceRoot?: string) {
        const configuration = getDsConfiguration();
        let workspaceConfig: WorkspaceConfiguration | undefined;
        if (workspaceRoot && (workspaceConfig = workspaces.get(workspaceRoot.toLowerCase()))) {
            let value = workspaceConfig.value();
            if (value.metadata) {
                configuration.metadata = value.metadata;
            }
            if (value["mdmSerializer.indentSpaceCount"] !== undefined) {
                configuration["mdmSerializer.indentSpaceCount"] = value["mdmSerializer.indentSpaceCount"];
            }
            configuration["mdmSerializer.lineFeedCharater"] = value["mdmSerializer.lineFeedCharater"];
        }
        return configuration;
    }

    function addWorkspace(workspaceRoot: string): WorkspaceConfiguration {
        let lowerPath = workspaceRoot.toLowerCase();
        let workspaceConfig = workspaces.get(lowerPath);
        if (workspaceConfig) {
            return workspaceConfig;
        }
        workspaceConfig = createWorkspaceConfiguration(workspaceRoot, fs.statSync(workspaceRoot).isDirectory());
        workspaceConfig.onPropertyJsonChanged(e => workspacePropertyJsonChangedEmitter.fire(e));
        workspaces.set(lowerPath, workspaceConfig);
        return workspaceConfig;
    }

    function removeWorkspace(workspaceRoot: string): void {
        workspaces.delete(workspaceRoot.toLowerCase());
    }

    function updateWorkspaceProperties<K extends keyof DsWorkspaceProperties>(workspaceRoot: string, key: K, value: DsWorkspaceProperties[K]): void {
        let workspace = workspaces.get(workspaceRoot.toLowerCase());
        if (workspace) {
            workspace.update(key, value);
        }
    }

    function getWorkspaceProperties(workspaceRoot: string): DsWorkspaceProperties {
        let storage = workspaces.get(workspaceRoot.toLowerCase());
        if (storage) {
            return storage.value();
        }
        return getDsConfiguration();
    }

    function dispose() {
        disposables.forEach(e => e.dispose());
        workspaces.forEach(config => config.dispose());
    }

}

